/*
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/

#include "cmdLine.h"
#include "cmdArg.h"
#include "cmdOption.h"
using namespace std;

const char* cmdLine::HELP_KEY = "--help";


cmdLine::cmdLine(const char* usage) : appName(NULL),  usageDesc(usage)
{
	Clear();
}

cmdLine::~cmdLine()
{
	
}

void cmdLine::Clear()
{
	for (OPT_ITERATOR pOpt = options.begin(); pOpt != options.end(); pOpt++) {
	
		
		try {
			delete (*pOpt).second;
		}
		catch (...) {
			
			string s;
			s += (*pOpt).first;
			ERROUT << ERR(ERR_DELETE) << " key: " << (*pOpt).first << ENDLN;
		}
		
	}
	
	for (ARG_ITERATOR pArg = args.begin(); pArg != args.end(); pArg++) {

		try {
			delete (*pArg);
		}
		catch (...) {
			
			ERROUT << ERR(ERR_DELETE) << "argument" << ENDLN;
		}
	}
	
}

void cmdLine::addOption(AbstractOption* opt)
{
	options[opt->Key()] = opt;
}

void cmdLine::addArg(AbstractArg* argument)
{
	args.push_back(argument);
}

void cmdLine::AddOption(char k, const char* desc)
{
	addOption(new cmdFlag(k, desc));
}
void cmdLine::AddOption(char k, const char* verboseKey, const char* desc)
{
	addOption(new cmdFlag(k, verboseKey, desc));
}

void cmdLine::AddStrArg(const char* desc/* = NULL*/, bool optional/* = true*/)
{
	addArg(new cmdArg<const char*, StringParser>(desc, optional));
}

void cmdLine::AddStrOption(char key, const char* verboseKey/* = NULL*/, const char* desc/* = NULL*/)
{
	addOption(new cmdOption<const char*>(key, verboseKey, desc, new cmdArg<const char*, StringParser>(NULL, true)));
}

bool cmdLine::IsKeySet(char key)
{
	if (AbstractOption::HasKey(key))
		return (*((cmdFlag*)options[key]) == true);
	return false;
}

bool cmdLine::IsKeySet(char* veboseKey)
{
	if (veboseKey == NULL)
		throw ERR_SW;
	
	for (OPT_ITERATOR pOpt = options.begin(); pOpt != options.end(); pOpt++) {
	
		string s((*pOpt).second->VerboseKey());
		if (s.find(veboseKey) != string::npos)
			return true;
	}
	return false;
}


bool cmdLine::HasOption(char key)
{
	return (AbstractOption::HasOption(key) && IsKeySet(key));
}

bool cmdLine::HasOption(char* veboseKey)
{
	char key = verbose2key(veboseKey);
	if (key != 0)
		return HasOption(key);

	return false;
}

char cmdLine::verbose2key(const char* veboseKey)
{
	for (OPT_ITERATOR pOpt = options.begin(); pOpt != options.end(); pOpt++) {
	
		string s((*pOpt).second->VerboseKey());
		if (s.find(veboseKey) != string::npos)
			return (*pOpt).second->Key();
	}
	return 0;
}

int cmdLine::OptCount()
{
	int n = 0;
	for (OPT_ITERATOR p = options.begin(); p != options.end(); p++) {
	
		if ((*p).second->Initialized())
			++n;
	}
	return n;
}

char* cmdLine::OptionKeyList()
{
	string s;
	for (OPT_ITERATOR p = options.begin(); p != options.end(); p++) {
	
		if ((*p).second->Initialized())
			s += (*p).second->Key();
	}
	return (char*)s.c_str();
	
}

int cmdLine::ArgCount() 
{
	int n = 0;
	for (ARG_ITERATOR p = args.begin(); p != args.end(); p++) {
	
		if ((*p)->Initialized())
			++n;
	}
	return n;
}


AbstractOption* cmdLine::GetOption(char key)
{
	return options[key];
}

AbstractArg* cmdLine::GetArg(int n)
{
	if (n < 0 || n > ArgCount())
		throw (ERR(ERR_OUT_OF_RANGE));
	return args[n];
}

// parse cmd line

ErrCode_t cmdLine::Parse(int argc, char **argv)
{
	size_t nArg = 0;
	size_t nOpts = 0;
	appName = argv[0]; 
	char expectValueOptId = 0;
	bool expecValue = false;
	// check for "--help"
	for(int n = 1; n < argc; n++) {
		
		if (strstr(argv[n], HELP_KEY)) { 
			PrintHelpMsg();
			exit(0);
		}
	}
		
	for(int n = 1; n < argc; n++)
	{
		string s = argv[n];
		trim(s);
		if (s[0] == '-') { // option or key or verbose option or verbose key
		
			if (expecValue) // expecting value but recieving another option
				return ERR_CMDLINE;
			DBGOUT << NEWLN << "Parsing " << s << endl; // done
			if (s.substr(0, 2) == "--") {
				
				char key = verbose2key(s.substr(2,1).c_str());
				if (AbstractOption::HasKey(key)) {
					cmdFlag *opt = (cmdFlag*)options[key];
					*opt = true;
					if (HasOption(key)) { // if it is -d 111 "111" will be in next argv; if it's -d111 we need to parse all remineder
					
						string vKey = opt->VerboseKey();
						int remindingChars = s.size() - 2 - vKey.size();
						if (remindingChars > 0) { // e.g. --bla123 try to parse remider of the argv
							
							string sub = s.substr(2 + vKey.size());
							DBGOUT << NEWLN << "Updating option " <<  opt->Key();
							DBGOUT << ", new value: " << sub; 
							options[key]->GetArg()->Set(argv[n], 2 + vKey.size());
							DBGOUT << "...done" << ENDLN; // done
							
						}
						else {
							
							expectValueOptId = opt->Key();
							expecValue = true;
						}
							
					}						
					DBGOUT << NEWLN << "Updating key " <<  opt->Key() << ENDLN; // done
					++nOpts;
				}
			}
			else {
				
				for (int pos = 1; pos < (int)s.size(); pos++) {
					
					if (AbstractOption::HasKey(s[pos])) {
						cmdFlag *opt = (cmdFlag*)options[s[pos]];
						*opt = true;
						if (HasOption(s[pos])) { // if it is -d 111 "111" will be in next argv; if it's -d111 we need to parse all remineder
						
							if (pos < (int)s.size() -2) { // try to parse remider of the argv
								DBGOUT << NEWLN << "Updating option " <<  opt->Key();
								DBGOUT << ", new value: " << (argv[n] + pos +1); 
								options[s[pos]]->GetArg()->Set(argv[n], pos +1);
								DBGOUT << "...done" << ENDLN; // done
								break;
							}
							else {
								
								expectValueOptId = opt->Key();
								expecValue = true;
							}
								
						}						
						DBGOUT << NEWLN << "Updating key " <<  opt->Key() << ENDLN; // done
						++nOpts;
					}
			}
			}
		}
		else if(expecValue) {
			
			DBGOUT << NEWLN << "Updating option " <<  options[expectValueOptId]->Key() << ", command line token: " << s;  
			options[expectValueOptId]->GetArg()->Set(argv[n]);
			expectValueOptId = 0;
			expecValue = false;
			DBGOUT << "...done" << ENDLN; // done
			continue;
		}
		else if (nArg < args.size()) { // arguments shall be in the order they added to cmdLine
		
			DBGOUT << NEWLN << "Updating argument " <<  nArg << ", new value: " << s; 
			args[nArg]->Set(argv[n]);
			if (args[nArg]->Initialized()) {
				DBGOUT << "...done" << ENDLN; // done
				nArg++;
			}
			else
				DBGOUT << "...failed" << ENDLN; // done
		}
		else {
			ERROUT << ERR_CMDLINE << " Don't know how to parse " << s << ENDLN;
			exit (-1);
		}
	}
	DBGOUT << NEWLN << nArg << " arguments and " << nOpts << " keys been processed" << ENDLN;
	// if argument has default value - it initialized already
	for (int n = nArg; n < (int)args.size(); n++) {
		if (args[n]->Initialized())
			nArg++;
	}
	
	if (nArg < args.size()) {
	
		DBGOUT << NEWLN <<  args.size() << " currently: " << nArg << endl;
		return ERR(ERR_NOTENOUGH_DATA);
	}
	return ERR_NONE;
}

void cmdLine::PrintHelpMsg(std::ostream& out/* = std::cout*/)
{
	
	out << appName << " [OPTION...] ";

	for (ARG_ITERATOR p = args.begin(); p != args.end(); p++) {
		
		(*p)->PrintDecs(out);
		cout << " ";
	}

	out << endl << usageDesc << endl;

	out << "Help Options: " << endl;
	out << "\t" << HELP_KEY << "           Show help options"<< endl << endl;

	if (options.size() > 0) {
	
		out << "Application Options: " << endl;
	
	
		int noVerboseOffset = 0;
		for (OPT_ITERATOR p = options.begin(); p != options.end(); p++) {
			
			int n = (*p).second->VerboseKeyLen();
			if (noVerboseOffset < n)
				noVerboseOffset = n;
			
		}
		for (OPT_ITERATOR p = options.begin(); p != options.end(); p++) {
			
			(*p).second->PrintDecs(noVerboseOffset, out); 
			cout << endl;
			
		}
	
		cout << endl << endl;
	}
	
}



